﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using NearForums.Configuration;

namespace NearForums.DataAccess.Xml
{
    class MessagesDataAccess : BaseDataAccess, IMessagesDataAccess
    {
        public void Add(Message message, string ip)
        {
            // start
            // messages are stored in the respective topic file
            // message count is available in the topics file

            string filePath = GetDataPath(TopicsFile);
            XDocument doc = XDocument.Load(filePath);
            int messageCount = (int)doc.Root.Attribute("MessageCount");
            message.Id = ++messageCount;
            // Update identity column
            doc.Root.SetAttributeValue("MessageCount", messageCount);
            doc.Save(filePath);  

            // put the message in the topic file
            string filePath2 = string.Format(GetDataPath(TopicFile), message.Topic.Id);
            XDocument doc2 = XDocument.Load(filePath2);
            XElement messages = doc2.Root.Element("Messages");

            XElement messageElem = new XElement("Message",
                                       new XElement("Id", message.Id),
                                       new XElement("Body", SiteConfiguration.HtmlEncoder(message.Body)),
                                       new XElement("CreationDate", ToString(DateTime.Now)),
                                       new XElement("LastEditDate", ToString(DateTime.Now)),
                                       new XElement("UserId", message.User.Id),
                                       new XElement("ParentId", (message.InReplyOf == null) ? null : (int?)message.InReplyOf.Id),
                                       new XElement("Active", true),
                                       new XElement("EditIp", ip),
                                       new XElement("LastEditUser", message.User.Id)
                                       );

            messages.AddFirst(messageElem);
            doc2.Save(filePath2);

            string dataPath = GetDataPath(MessagesFile);
            XDocument messagesMetaDoc = XDocument.Load(dataPath);
            XElement messageMetaElem = new XElement("Message",
                                        new XAttribute("Id", message.Id),
                                        new XAttribute("TopicId", message.Topic.Id),
                                        new XAttribute("UserId", message.User.Id),
                                        new XElement("Flags"));
            messagesMetaDoc.Root.Add(messageMetaElem);
            messagesMetaDoc.Save(dataPath);


            TopicsDataAccess tda = new TopicsDataAccess();
            tda.AddReply(message.Topic.Id);

            ForumsDataAccess fda = new ForumsDataAccess();
            fda.AddMessage(message.Topic.Forum.Id);

            
        }

        public bool ClearFlags(int topicId, int messageId)
        {
            var filePath = GetDataPath(MessagesFile);
            var doc = XDocument.Load(filePath);
            var qry = from m in doc.Root.Elements("Message")
                      where (int)m.Attribute("Id") == messageId
                      select m;
            var elem = qry.SingleOrDefault();
            if (elem != null)
            {
                elem.Element("Flags").RemoveAll();
                doc.Save(filePath);
                return true;
            }
            return false;
        }

        public void Delete(int topicId, int messageId, int userId)
        {
            string topicPath = string.Format(GetDataPath(TopicFile), topicId);
            XDocument topicDoc = XDocument.Load(topicPath);
            var messagesElem = topicDoc.Root.Element("Messages");
            var msgQry = from msg in messagesElem.Elements("Message")
                         where (int)msg.Element("Id") == messageId
                         select msg;
            var messageElem = msgQry.SingleOrDefault();
            if (messageElem != null)
            {
                messageElem.Remove();
                topicDoc.Save(topicPath);
            }

            string messagesPath = GetDataPath(MessagesFile);
            XDocument messagesMetaDoc = XDocument.Load(messagesPath);
            var metaQry = from meta in messagesMetaDoc.Root.Elements("Message")
                          where (int)meta.Attribute("Id") == messageId
                          select meta;

            XElement messageMetaElem = metaQry.SingleOrDefault();
            if (messageMetaElem != null)
            {
                messageMetaElem.Remove();
                messagesMetaDoc.Save(messagesPath);
            }

            TopicsDataAccess tda = new TopicsDataAccess();
            tda.AddReply(topicId, -1);
        }

        public bool Flag(int topicId, int messageId, string ip)
        {
            // messageId is sufficent for flagging
            string filePath = GetDataPath(MessagesFile);
            XDocument doc = XDocument.Load(filePath);
            var qry = from m in doc.Root.Elements("Message")
                      where (int)m.Attribute("Id") == messageId
                      select m;

            var elem = qry.SingleOrDefault();
            if (elem != null)
            {
                var flagElem = new XElement("Flag",
                                    new XAttribute("Ip", ip),
                                    new XAttribute("Date", ToString(DateTime.Now)));
                elem.Element("Flags").Add(flagElem);
                doc.Save(filePath);
                return true;
            }

            return false;
        }

        public List<Message> GetByTopic(int topicId)
        {
            string filePath = string.Format(GetDataPath(TopicFile), topicId);
            XDocument doc = XDocument.Load(filePath);
            var qry = from m in doc.Root.Element("Messages").Elements("Message")
                      orderby (int)m.Element("Id") descending
                      select m;
            List<Message> messages = new List<Message>();
            foreach (var elem in qry)
            {
                Message message = GetMessageFromElement(elem, topicId, true);
                messages.Add(message);
            }
            return messages;
        }

        public List<Message> GetByTopic(int topicId, int firstMsg, int lastMsg)
        {
            return GetByTopic(topicId, firstMsg, (lastMsg - firstMsg + 1));
        }

        public List<Message> GetByTopicFrom(int topicId, int firstMsg, int amount)
        {
            // assume msgs are sorted by message id descending
            string filePath = string.Format(GetDataPath(TopicFile), topicId);
            XDocument doc = XDocument.Load(filePath);
            var qry = from m in doc.Root.Element("Messages").Elements("Message")
                      orderby (int)m.Element("Id") descending
                      select m;

            IEnumerable<XElement> msgElems;
            if (firstMsg > 1)
                msgElems = qry.Skip(firstMsg - 1).Take(amount);
            else
                msgElems = qry.Take(amount);

            List<Message> messages = new List<Message>();
            foreach (var msgElem in msgElems)
            {
                var message = GetMessageFromElement(msgElem, topicId);
                messages.Add(message);
            }

            return messages;
        }

        public List<Message> GetByTopicLatest(int topicId)
        {
            return GetByTopic(topicId);
        }

        public List<Topic> ListFlagged()
        {
            XDocument doc = XDocument.Load(GetDataPath(MessagesFile));
            var qry = from m in doc.Root.Elements("Message")
                      where m.Element("Flags").Elements("Flag").Count() > 0
                      select m;

            var tda = new TopicsDataAccess();
            
            var messageIds = new List<int>();
            var topicMap = new Dictionary<int, Topic>();
            foreach (var msgElem in qry)
            {
                int messageId = (int)msgElem.Attribute("Id");
                messageIds.Add(messageId);
                int topicId = (int)msgElem.Attribute("TopicId");
                Topic topic = null;
                if (topicMap.ContainsKey(topicId))
                {
                    topic = topicMap[topicId];
                }
                else
                {
                    topic = tda.Get(topicId, false);
                    topicMap[topicId] = topic;
                }
                
                if (topic.Messages == null)
                    topic.Messages = new List<Message>();

                topic.Messages.Add(GetMessage(topicId, messageId));
            }

            var topics = new List<Topic>();
            foreach (var topic in topicMap.Values)
                topics.Add(topic);

            return topics;
        }

        private Message GetMessageFromElement(XElement msgElem, int topicId, bool noTopic = false)
        {
            TopicsDataAccess tda = new TopicsDataAccess();
            UsersDataAccess uda = new UsersDataAccess();

            var message = new Message();
            message.Id = (int)msgElem.Element("Id");
            if (!noTopic)
                message.Topic = tda.Get(topicId);
            message.Body = SiteConfiguration.HtmlDecoder((string)msgElem.Element("Body"));
            message.Date = ToTime((string)msgElem.Element("CreationDate"));
            message.User = uda.Get((int)msgElem.Element("UserId"));
            message.Active = (bool)msgElem.Element("Active");
            if (!noTopic)
                message.InReplyOf = ((string)msgElem.Element("ParentId") == string.Empty) ? null : GetMessage(topicId, (int)msgElem.Element("ParentId"));
            return message;
        }

        internal Message GetMessage(int topicId, int messageId)
        {
            string filePath = string.Format(GetDataPath(TopicFile), topicId);
            XDocument doc = XDocument.Load(filePath);
            var qry = from m in doc.Root.Element("Messages").Elements("Message")
                      where (int)m.Element("Id") == messageId
                      select m;
            var msgElem = qry.SingleOrDefault();
            if (msgElem != null)
                return GetMessageFromElement(msgElem, topicId);

            return null;
        }

        internal Message GetLastMessage(int topicId)
        {
            string filePath = string.Format(GetDataPath(TopicFile), topicId);
            XDocument doc = XDocument.Load(filePath);
            var qry = from m in doc.Root.Element("Messages").Elements("Message")
                      orderby (int)m.Element("Id") descending
                      select m;
            var msgElem = qry.FirstOrDefault();
            if (msgElem != null)
                return GetMessageFromElement(msgElem, topicId, true);

            return null;
        }
    }
}
